package com.automapper.execute.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.automapper.constants.ParameterConstants;
import com.automapper.execute.Execute;
import com.automapper.query.CriteriaImpl;
import com.automapper.sqlbuilder.SqlBuilderTemplate;
import com.automapper.sqlfragment.SqlFragmentAssembler;
import com.automapper.sqlfragment.SqlFragmentContext;
import com.automapper.utils.FieldFacade;
import com.automapper.utils.ObjectFacade;
import com.automapper.utils.PropertiesFacade;
import com.automapper.utils.SqlSourceBuilderUtils;

public class SelectExecute implements Execute {

	private static final Logger logger = LoggerFactory.getLogger(SelectExecute.class);
	private static final String AUTO_MAPPER = "AutoMapper_";
	
	private SqlBuilderTemplate sqlBuilder;
	
	@Override
	public void process(MetaObject metaStatementHandler, Invocation invocation, PropertiesFacade propertiesFacade) throws Exception {
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
		
		//获取入库数据
		Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
		
		Map<String, Object> parameter = (Map<String, Object>) parameterObject; 
		CriteriaImpl criteria = (CriteriaImpl) parameter.get(ParameterConstants.PARAM);
		SqlFragmentContext context = criteria.getSqlFragmentContext();
		
		SqlFragmentAssembler driverSqlFragmentAssembler = context.getDriverSqlFragmentAssembler();
		SqlFragmentAssembler cascadeSqlFragmentAssembler = driverSqlFragmentAssembler.getCascadeSqlFragmentAssembler();
		Class<?> cascadeClazz = null;
		String nestedResultMapId = null;
		if (cascadeSqlFragmentAssembler != null) {
			cascadeClazz = cascadeSqlFragmentAssembler.getOriginalClass();
			int errorNum = 0;
			this.setCascadeResultMap(configuration, cascadeSqlFragmentAssembler, errorNum);
			nestedResultMapId = genResultId(cascadeSqlFragmentAssembler);
		}
		
		String resultMapId = genResultId(driverSqlFragmentAssembler);
		//定义ResultMap，父表ResultMap对象是存放MappedStatement
		List<ResultMap> resultMaps = new ArrayList<>();
	    resultMaps.add(getResultMap(configuration, driverSqlFragmentAssembler.getOriginalClass(), cascadeClazz, driverSqlFragmentAssembler.getAlias(), resultMapId, nestedResultMapId));
	    
	    MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);	
        metaObject.setValue("resultMaps", Collections.unmodifiableList(resultMaps));
        
		String newSql = sqlBuilder.buildSql(parameterObject, propertiesFacade);
		logger.debug("Auto generated sql:{}", newSql);
		
		SqlSource sqlSource = SqlSourceBuilderUtils.buildSqlSource(configuration, newSql, parameterObject.getClass());
		List<ParameterMapping> parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();
		metaStatementHandler.setValue("delegate.boundSql.sql", sqlSource.getBoundSql(parameterObject).getSql());
		metaStatementHandler.setValue("delegate.boundSql.parameterMappings", parameterMappings);
	}
	
	private ResultMap getResultMap(Configuration configuration, Class<?> clazz, Class<?> cascadeClazz, String alias,String resultMapId, String nestedReusltMapId) throws Exception {
		ObjectFacade objectFacade = new ObjectFacade(clazz.newInstance());
		List<ResultMapping> resultMappings = new ArrayList<>();
		for (FieldFacade fieldFacade : objectFacade.getFieldFacades()) {
			//不需要级联查询，跳过生成ResultMaping对象
			if (fieldFacade.hasNestedResults() && cascadeClazz == null) {
				continue;
			} 
			
			//生成ResultMap里面每个节点
			ResultMapping.Builder builder = new ResultMapping.Builder(configuration, fieldFacade.getFieldName(), getColumnName(fieldFacade, alias), fieldFacade.getJavaType());
			List<ResultFlag> flags = new ArrayList<>();
			if (fieldFacade.isId()) {
				flags.add(ResultFlag.ID);
			}
			builder.flags(flags);
			
			if (fieldFacade.hasNestedResults()) {
				//<collection>节点的resultMap属性值，和其他<resultMap>的id属性关联
				builder.nestedResultMapId(nestedReusltMapId);
			}
			
            resultMappings.add(builder.build());
		}
		//生成一个完整的ResultMap
		ResultMap.Builder builder = new ResultMap.Builder(configuration, resultMapId, clazz, resultMappings, true);
		return builder.build();
	}
	
	private String getColumnName(FieldFacade fieldFacade, String alias) {
		if (fieldFacade.hasNestedResults()) {
			return "";
		}
		return alias + fieldFacade.getColumnName();
	}
	
	private void setCascadeResultMap(Configuration configuration,SqlFragmentAssembler sqlFragmentAssembler, int errorNum) throws Exception {
		try {
			//生成key检查该key的ResultMap在Configuration里是否存在，存在则不需执行Configuration.addResultMap。
			//如果将存放相同key，则出现 mybatis result maps collection already contains value异常
			//因为在result标签的type值和typeAlias标签的alias值不能相同
			String key = genResultId(sqlFragmentAssembler);
			if (!configuration.hasResultMap(key)) {
				// 定义ResultMap，子表ResultMap对象是存放Configuration，通过父表ResultMap的conllection标签查找子表的ResultMap
				ResultMap resultMap = this.getResultMap(configuration, sqlFragmentAssembler.getOriginalClass(), null,sqlFragmentAssembler.getAlias(), key, null);
				configuration.addResultMap(resultMap);
			}
		} catch (Exception e) {
			// 初始化configuration的ResultMap对象时，在并发请求时容易出现异常
			// 所以在出现异常时候，重试3次，3次失败后抛异常
			if (errorNum >= 2) {
				logger.error("configuration.addResultMap 执行超过3次失败", e);
				throw e;
			}
			++errorNum;
			setCascadeResultMap(configuration, sqlFragmentAssembler, errorNum);
		}
	}
	
	private String genResultId(SqlFragmentAssembler sqlFragmentAssembler) {
		Class<?> cascadeClazz = sqlFragmentAssembler.getOriginalClass();
		return AUTO_MAPPER + cascadeClazz.getName() + sqlFragmentAssembler.getAlias();
	}
}
